package com.mq.mqclient;

import com.mq.common.Arguments.BasicArguments;
import com.mq.common.Arguments.ExchangeDeclareArguments;
import com.mq.common.Arguments.QueueDeclareArguments;
import com.mq.common.BasicReturns;
import com.mq.common.BinaryTool;
import com.mq.common.Consumer;
import com.mq.common.Request;
import com.mq.mqserver.core.BasicProperties;
import com.mq.mqserver.core.ExchangeType;
import com.mq.common.Arguments.ExchangeDeleteArguments;
import com.mq.common.Arguments.QueueDeleteArguments;
import com.mq.common.Arguments.QueueBindArguments;
import com.mq.common.Arguments.QueueUnbindArguments;
import com.mq.common.Arguments.BasicPublishArguments;
import com.mq.common.Arguments.BasicConsumeArguments;
import com.mq.common.exception.MqException;
import com.mq.common.Arguments.BasicAckArguments;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/*
* 逻辑上的连接
* 一个客户端可以有多个模块，每个模块和服务器建立逻辑上连接
* 这几个模块的channel 彼此之间是互不影响的
* 但是这几个 channel 复用了同一个TCP连接
*
* 提供一系列方法，和服务器提供的核心API对应
* 这些方法，方法的内部，就是发一个特定的请求*/
public class Channel {
    private String channelId;
    //当前channel 属于哪个连接
    private Connection connection;
    // key 为 rid, 即 requestId / responseId.
    //存储后续客户端收到的服务器响应
    private ConcurrentHashMap<String, BasicReturns> baseReturnsMap = new ConcurrentHashMap<>();
    // 如果当前 channel 订阅了某个队列，就需要记录回调是啥，当该队列消息返回回来的时候，调用回调
    //此处约定一个 channel 中只能有一个回调
    private Consumer consumer = null;

    public String getChannelId() {
        return channelId;
    }

    public void setChannelId(String channelId) {
        this.channelId = channelId;
    }

    public Connection getConnection() {
        return connection;
    }

    public void setConnection(Connection connection) {
        this.connection = connection;
    }

    public ConcurrentHashMap<String, BasicReturns> getBaseReturnsMap() {
        return baseReturnsMap;
    }

    public void setBaseReturnsMap(ConcurrentHashMap<String, BasicReturns> baseReturnsMap) {
        this.baseReturnsMap = baseReturnsMap;
    }

    public Consumer getConsumer() {
        return consumer;
    }

    public void setConsumer(Consumer consumer) {
        this.consumer = consumer;
    }

    public Channel(String channelId, Connection connection) {
        this.channelId = channelId;
        this.connection = connection;
    }
    //和服务器进行交互，告知服务器，此处客户端创建了新的channel
    //一个 channel 一个请求，对应一个回应
    public boolean createChannel() throws IOException {
        BasicArguments basicArguments = new BasicArguments();
        basicArguments.setRid(generateRid()); // 使用uuid
        basicArguments.setChannelId(channelId);
        byte[] payload = BinaryTool.toBytes(basicArguments);

        Request request = new Request();
        request.setType(0x1);
        request.setLength(payload.length);
        request.setPayload(payload);

        //构造出完整的请求之后，就可以发送这个请求了
        connection.writeRequest(request);
        // 阻塞等待服务器的响应
        BasicReturns baseReturns = waitResult(basicArguments.getRid());
        return baseReturns.isOk();

    }
    //使用这个方法来阻塞等待服务器的响应
    private BasicReturns waitResult(String rid) {
        BasicReturns basicReturns = null;
        while ((basicReturns = baseReturnsMap.get(rid)) == null) {
            //还没有收到响应，等待
            synchronized (this) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    // 如果 wait 被提前唤醒, 也应该继续循环.
                    // 所以这⾥啥都不⼲, 但是 try 需要放到 while 内部.
                    e.printStackTrace();
                }
            }
        }
        //读取成功之后，还需要把消息从哈希表中删除掉
        baseReturnsMap.remove(rid);
        return basicReturns;
    }
    //唤醒阻塞进程
    public void putReturns(BasicReturns basicReturns) {
        baseReturnsMap.put(basicReturns.getRid(), basicReturns);
        synchronized (this) {
            // 这⾥要唤醒所有等待的线程, 不能只唤醒⼀个.
            //当前不知道有多少线程在等待响应
            notifyAll();
        }
    }

    private String generateRid() {
        return "R-" + UUID.randomUUID().toString();
    }
    public boolean close() throws IOException {
        BasicArguments basicArguments = new BasicArguments();
        basicArguments.setRid(generateRid());
        basicArguments.setChannelId(channelId);
        byte[] playload = BinaryTool.toBytes(basicArguments);

        Request request = new Request();
        request.setType(0x2);
        request.setLength(playload.length);
        request.setPayload(playload);

        connection.writeRequest(request);
        BasicReturns basicReturns = waitResult(basicArguments.getRid());
        return basicReturns.isOk();
    }

    public boolean exchangeDeclare(String exchangeName, ExchangeType exchangeType,
                                   boolean durable, boolean autoDelete,
                                   Map<String, Object> arguments) throws IOException {
        ExchangeDeclareArguments exchangeDeclareArguments = new ExchangeDeclareArguments();

        exchangeDeclareArguments.setRid(generateRid());
        exchangeDeclareArguments.setChannelId(channelId);

        exchangeDeclareArguments.setExchangeName(exchangeName);
        exchangeDeclareArguments.setExchangeType(exchangeType);
        exchangeDeclareArguments.setDurable(durable);
        exchangeDeclareArguments.setAutoDelete(autoDelete);
        exchangeDeclareArguments.setArguments(arguments);

        byte[] payload = BinaryTool.toBytes(exchangeDeclareArguments);

        Request request = new Request();
        request.setType(0x3);
        request.setLength(payload.length);
        request.setPayload(payload);
        connection.writeRequest(request);

        BasicReturns basicReturns = waitResult(exchangeDeclareArguments.getRid());
        return basicReturns.isOk();
    }
    public boolean exchangeDelete(String exchangeName) throws IOException {
        ExchangeDeleteArguments exchangeDeleteArguments = new
                ExchangeDeleteArguments();
        exchangeDeleteArguments.setRid(generateRid());
        exchangeDeleteArguments.setChannelId(channelId);
        exchangeDeleteArguments.setExchangeName(exchangeName);

        byte[] payload = BinaryTool.toBytes(exchangeDeleteArguments);

        Request request = new Request();
        request.setType(0x4);
        request.setLength(payload.length);
        request.setPayload(payload);
        connection.writeRequest(request);
        // 阻塞等待服务器的响应
        BasicReturns basicReturns = waitResult(exchangeDeleteArguments.getRid());
        return basicReturns.isOk();
    }
    public boolean queueDeclare(String queueName, boolean durable, boolean
            exclusive, boolean autoDelete,
                                Map<String, Object> arguments) throws IOException {
        QueueDeclareArguments queueDeclareArguments = new QueueDeclareArguments();
        queueDeclareArguments.setRid(generateRid());
        queueDeclareArguments.setChannelId(channelId);
        queueDeclareArguments.setQueueName(queueName);
        queueDeclareArguments.setDurable(durable);
        queueDeclareArguments.setExclusive(exclusive);
        queueDeclareArguments.setAutoDelete(autoDelete);
        queueDeclareArguments.setArguments(arguments);
        byte[] payload = BinaryTool.toBytes(queueDeclareArguments);
        Request request = new Request();
        request.setType(0x5);
        request.setLength(payload.length);
        request.setPayload(payload);
        connection.writeRequest(request);
        // 阻塞等待服务器的响应
        BasicReturns basicReturns = waitResult(queueDeclareArguments.getRid());
        return basicReturns.isOk();
    }
    public boolean queueDelete(String queueName) throws IOException {
        QueueDeleteArguments queueDeleteArguments = new QueueDeleteArguments();
        queueDeleteArguments.setRid(generateRid());
        queueDeleteArguments.setChannelId(channelId);
        queueDeleteArguments.setQueueName(queueName);
        byte[] payload = BinaryTool.toBytes(queueDeleteArguments);
        Request request = new Request();
        request.setType(0x6);
        request.setLength(payload.length);
        request.setPayload(payload);
        connection.writeRequest(request);
        // 阻塞等待服务器的响应
        BasicReturns basicReturns = waitResult(queueDeleteArguments.getRid());
        return basicReturns.isOk();
    }
    // 对于直接交换机和 fanout 交换机, bindingKey 不⽣效. 直接设为 "" 即可
    public boolean queueBind(String queueName, String exchangeName) throws
            IOException {
        return queueBind(queueName, exchangeName, "");
    }
    public boolean queueBind(String queueName, String exchangeName, String
            bindingKey) throws IOException {
        QueueBindArguments queueBindArguments = new QueueBindArguments();
        queueBindArguments.setRid(generateRid());
        queueBindArguments.setChannelId(channelId);
        queueBindArguments.setQueueName(queueName);
        queueBindArguments.setExchangeName(exchangeName);
        queueBindArguments.setBindingKey(bindingKey);
        byte[] payload = BinaryTool.toBytes(queueBindArguments);
        Request request = new Request();
        request.setType(0x7);
        request.setLength(payload.length);
        request.setPayload(payload);
        connection.writeRequest(request);
        // 阻塞等待服务器的响应
        BasicReturns basicReturns = waitResult(queueBindArguments.getRid());
        return basicReturns.isOk();
    }
    public boolean queueUnbind(String queueName, String exchangeName) throws
            IOException {
        QueueUnbindArguments queueUnbindArguments = new QueueUnbindArguments();
        queueUnbindArguments.setRid(generateRid());
        queueUnbindArguments.setChannelId(channelId);
        queueUnbindArguments.setQueueName(queueName);
        queueUnbindArguments.setExchangeName(exchangeName);
        byte[] payload = BinaryTool.toBytes(queueUnbindArguments);
        Request request = new Request();
        request.setType(0x8);
        request.setLength(payload.length);
        request.setPayload(payload);
        connection.writeRequest(request);
        // 阻塞等待服务器的响应
        BasicReturns basicReturns = waitResult(queueUnbindArguments.getRid());
        return basicReturns.isOk();
    }
    public boolean basicPublish(String exchangeName, String routingKey,
                                BasicProperties basicProperties, byte[] body) throws IOException {
        BasicPublishArguments basicPublishArguments = new BasicPublishArguments();
        basicPublishArguments.setRid(generateRid());
        basicPublishArguments.setChannelId(channelId);
        basicPublishArguments.setExchangeName(exchangeName);
        basicPublishArguments.setRoutingKey(routingKey);
        basicPublishArguments.setBasicProperties(basicProperties);
        basicPublishArguments.setBody(body);
        byte[] payload = BinaryTool.toBytes(basicPublishArguments);
        Request request = new Request();
        request.setType(0x9);
        request.setLength(payload.length);
        request.setPayload(payload);
        connection.writeRequest(request);
        // 阻塞等待服务器的响应
        BasicReturns basicReturns = waitResult(basicPublishArguments.getRid());
        return basicReturns.isOk();
    }
    public boolean basicConsume(String queueName, boolean autoAck, Consumer
            consumer) throws IOException, MqException {
        BasicConsumeArguments basicConsumeArguments = new BasicConsumeArguments();
        basicConsumeArguments.setRid(generateRid());
        basicConsumeArguments.setChannelId(channelId);
        basicConsumeArguments.setQueueName(queueName);
        basicConsumeArguments.setAutoAck(autoAck);
        basicConsumeArguments.setConsumeTag(channelId);
        byte[] payload = BinaryTool.toBytes(basicConsumeArguments);
        Request request = new Request();
        request.setType(0xa);
        request.setLength(payload.length);
        request.setPayload(payload);
        connection.writeRequest(request);
        // 阻塞等待服务器的响应
        BasicReturns basicReturns = waitResult(basicConsumeArguments.getRid());
        if (basicReturns.isOk()) {
            // 设置回调
            if (this.consumer != null) {
                throw new MqException("该 channel 已经设置过消费回调, 不能重复设置!");
            }
            this.consumer = consumer;
        }
        return basicReturns.isOk();
    }
    public boolean basicAck(String queueName, String messageId) throws IOException {
        BasicAckArguments basicAckArguments = new BasicAckArguments();
        basicAckArguments.setRid(generateRid());
        basicAckArguments.setChannelId(channelId);
        basicAckArguments.setQueueName(queueName);
        basicAckArguments.setMessageId(messageId);
        byte[] payload = BinaryTool.toBytes(basicAckArguments);
        Request request = new Request();
        request.setType(0xb);
        request.setLength(payload.length);
        request.setPayload(payload);
        connection.writeRequest(request);
        // 阻塞等待服务器的响应
        BasicReturns basicReturns = waitResult(basicAckArguments.getRid());
        return basicReturns.isOk();
    }

}
